﻿namespace EFS.RC.CC.Presences.Operators.Distribution
{
	/// <summary>
	/// Distribution list for ACD Agents
	/// </summary>
	internal class List : EFS.Common.ISelfDescriptor
	{
		#region Composition

		// async access
		private object syncAccess = new object();

		// Distribution list
		System.Collections.Generic.List<EFS.RC.CC.Presences.Operators.Distribution.Agent> agentsDistributionList
			= new System.Collections.Generic.List<EFS.RC.CC.Presences.Operators.Distribution.Agent>();

		// End point
		EFS.RC.CC.Applications.EndPoints.OfficeService.Customers customersOfficeServiceEndPointApplication = null;

		// Our initial agents 
		System.Collections.Generic.List<System.Int32> deploymentDistributedAgentsID = new System.Collections.Generic.List<System.Int32>();

		#endregion

		#region Constructor

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="operatorsDistribution"></param>
		/// <param name="operatorsOfficeServiceEndPointApplication"></param>
		public List(EFS.RC.CC.Applications.EndPoints.OfficeService.Customers customersOfficeServiceEndPointApplication)
		{
			// check
			EFS.Common.Verify.Reference(customersOfficeServiceEndPointApplication, "customersOfficeServiceEndPointApplication");

			// set
			this.customersOfficeServiceEndPointApplication = customersOfficeServiceEndPointApplication;
		}

		#endregion

		#region Usage

		/// <summary>
		/// Describe this
		/// </summary>
		/// <returns></returns>
		public System.String Describe()
		{
			return System.String.Format("EFS.RC.CC.Presences.Operators.Distribution.List [{0}]", customersOfficeServiceEndPointApplication.Describe());
		}

		// Customer End point
		public EFS.RC.CC.Applications.EndPoints.OfficeService.Customers CustomersOfficeServiceEndPointApplication
		{
 			get { return customersOfficeServiceEndPointApplication; }
		}

        /// <summary>
        /// Load skills map
        /// </summary>
        public void LoadInitialDistribution()
        {
            // get distribution
            EFS.RC.CC.Deployment.Entities.Operators.Distribution[] operatorsDistributionArray = EFS.RC.CC.Deployment.Tables.Tools.AgentDistributions.GetByCustomerEndPointID(
                customersOfficeServiceEndPointApplication.ApplicationPresence.PresenceEndPointDeployment.ID.Value);

            // check
            EFS.Common.Verify.Reference(operatorsDistributionArray, "operatorsDistributionArray");

            // lock
            // ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
            lock (syncAccess)
            {
                // add all to our list
                foreach (EFS.RC.CC.Deployment.Entities.Operators.Distribution operatorDistribution in operatorsDistributionArray)
                {
                    // add to list
                    deploymentDistributedAgentsID.Add(operatorDistribution.AgentID.Value);
                }
            }

            // get skills and make our map
            customersOfficeServiceEndPointApplication.OfficeServiceOperatorsEndPointApplication.OperatorsTeam.ForEachAgent(StartAgentForInitialDistribution);
        }

		/// <summary>
		/// Distribute one of available agents
		/// </summary>
		/// <param name="availableAgents"></param>
		/// <returns></returns>
		public System.Collections.Generic.List<EFS.RC.CC.Presences.Operators.Distribution.Agent> 
			Distribute(System.Collections.Generic.List<EFS.RC.CC.Presences.Operators.OfficeService.Agent> availableAgents)
		{
			// check
			EFS.Common.Verify.Reference(availableAgents, "availableAgents");

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(null, "Distribute", this); 

			// found agents
			System.Collections.Generic.List<EFS.RC.CC.Presences.Operators.Distribution.Agent> proposedAgentsDistrubution = 
				new System.Collections.Generic.List<EFS.RC.CC.Presences.Operators.Distribution.Agent>();

			// loack access
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// cycle in order of required skill
				foreach (Agent agentDistribution in agentsDistributionList)
				{
					//  skills
					if (availableAgents.Contains(agentDistribution.AgentPresence))
					{
						proposedAgentsDistrubution.Add(agentDistribution);
					}
				}
			}

			// done
			return proposedAgentsDistrubution;
		}

		/// <summary>
		/// Register presence for distribution
		/// </summary>
		/// <param name="agentPresence"></param>
		public void AddAgentToDistribution(EFS.RC.CC.Presences.Operators.OfficeService.Agent agentPresence)
		{
			// check
			EFS.Common.Verify.Reference(agentPresence, "agentPresence");

			// start for distribution
			StartAgentForDistribution(agentPresence);
		}

		#endregion

		#region Runtime

		/// <summary>
		/// Register presence for distribution
		/// </summary>
		/// <param name="agentPresence"></param>
		private void StartAgentForInitialDistribution(EFS.RC.CC.Presences.Operators.OfficeService.Agent agentPresence)
		{
			// check
			EFS.Common.Verify.Reference(agentPresence, "agentPresence");

			// lock
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check if in our distribution
				if (!deploymentDistributedAgentsID.Contains(agentPresence.OperatorPresenceDeployment.ID.Value))
				{
					return;
				}
			}

			// start
			StartAgentForDistribution(agentPresence);
		}

		/// <summary>
		/// Register presence for distribution
		/// </summary>
		/// <param name="agentPresence"></param>
		private void StartAgentForDistribution(EFS.RC.CC.Presences.Operators.OfficeService.Agent agentPresence)
		{
			// check
			EFS.Common.Verify.Reference(agentPresence, "agentPresence");

			// make distribution
			EFS.RC.CC.Presences.Operators.Distribution.Agent agentDistribution = agentPresence.RegisterForDistribution(this);

			// check
			if (null != agentDistribution)
			{
				// register for ready
				agentDistribution.OnDistributeReady += AddDistribution;
				agentDistribution.OnDistributeStopped += RemoveDistribution;

				// begin
				agentDistribution.BeginDistribute();
			}
		}

		/// <summary>
		/// Add distribution
		/// </summary>
		/// <param name="agentDeploymentId"></param>
		private void AddDistribution(EFS.RC.CC.Presences.Operators.Distribution.Agent agentDistribution)
		{
			// check
			EFS.Common.Verify.Reference(agentDistribution, "agentDistribution");

			// check 
			if (!agentDistribution.AgentsDistribution.Equals(this))
			{
				// wrong
				throw new System.InvalidOperationException("Register different distribution");
			}

			// loack access
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check
				if (!agentsDistributionList.Contains(agentDistribution))
				{
					// add
					agentsDistributionList.Add(agentDistribution);

					// sort
					agentsDistributionList.Sort();
				}
			}
		}

		/// <summary>
		/// Remove distribution
		/// </summary>
		/// <param name="agentDistribution"></param>
		private void RemoveDistribution(EFS.RC.CC.Presences.Operators.Distribution.Agent agentDistribution)
		{
			// check
			EFS.Common.Verify.Reference(agentDistribution, "agentDistribution");

			// check 
			if (!agentDistribution.AgentsDistribution.Equals(this))
			{
				// wrong
				throw new System.InvalidOperationException("Remove different distribution");
			}

			// loack access
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check
				if (agentsDistributionList.Contains(agentDistribution))
				{
					// un-register events
					agentDistribution.OnDistributeStopped -= RemoveDistribution;
					agentDistribution.OnDistributeReady -= AddDistribution;

					// add
					agentsDistributionList.Remove(agentDistribution);
				}
			}
		}

		#endregion
	}
}
